/*
 * Copyright 2010-2012 VMware and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package cn.wenhao.javaClassReload.jvm;

import java.util.Collection;
import java.util.List;

import org.objectweb.asm.Type;

import cn.wenhao.javaClassReload.utils.Utils;

/**
 * To manage the complexity of the different cases created by a variety of different types of contexts where we can do
 * 'method lookup' we need an abstraction to represent them all.
 * <p>
 * This class provides that abstraction.
 * 
 */
public abstract class MethodProvider {

    public static MethodProvider create(ReloadableType rtype) {
        return new ReloadableTypeMethodProvider(rtype);
    }

    public static MethodProvider create(TypeRegistry registry, TypeDescriptor typeDescriptor) {
        if (typeDescriptor.isReloadable()) {
            ReloadableType rtype = registry.getReloadableType(typeDescriptor.getName(), false);
            if (rtype == null) {
                TypeRegistry tr = registry;
                while (rtype == null) {
                    ClassLoader pcl = tr.getClassLoader().getParent();
                    if (pcl == null) {
                        break;
                    } else {
                        tr = TypeRegistry.getTypeRegistryFor(pcl);
                        if (tr == null) {
                            break;
                        }
                        rtype = tr.getReloadableType(typeDescriptor.getName(), false);
                    }
                }
            }
            if (rtype != null) {
                return new ReloadableTypeMethodProvider(rtype);
            }

            // ReloadableType rtype = registry.getReloadableType(typeDescriptor.getName(), true);
            // // TODO rtype can be null if this type hasn't been loaded yet for the first time, is that true?
            // // e.g. CGLIB generated proxy for a service type in grails
            // if (rtype != null) {
            // return new ReloadableTypeMethodProvider(rtype);
            // }
        }
        try {
            try {
                Type objectType = Type.getObjectType(typeDescriptor.getName());

                // TODO doing things this way would mean we aren't 'guessing' the delegation strategy, we
                // are instead allowing it to do its thing then looking for the right registry.
                // Above we are guessing regular parent delegation.
                Class<?> class1 = Utils.toClass(objectType, registry.getClassLoader());
                if (typeDescriptor.isReloadable()) {
                    ClassLoader cl = class1.getClassLoader();
                    TypeRegistry tr = TypeRegistry.getTypeRegistryFor(cl);
                    ReloadableType rtype = tr.getReloadableType(typeDescriptor.getName(), true);
                    if (rtype != null) {
                        return new ReloadableTypeMethodProvider(rtype);
                    }
                }
                return create(class1);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(
                    "We have a type descriptor for '" + typeDescriptor.getName() + " but no corresponding Java class",
                    e);
            }
        } catch (RuntimeException re) {
            re.printStackTrace();
            throw re;
        }
    }

    public static MethodProvider create(Class<?> clazz) {
        return new JavaClassMethodProvider(clazz);
    }

    public abstract List<Invoker> getDeclaredMethods();

    public abstract MethodProvider getSuper();

    public abstract MethodProvider[] getInterfaces();

    public abstract boolean isInterface();

    public abstract String getSlashedName();

    /**
     * @return Full qualified name with "."
     */
    public String getDottedName() {
        return getSlashedName().replace('/', '.');
    }

    public Invoker dynamicLookup(int mods, String name, String methodDescriptor) {
        return new DynamicLookup(name, methodDescriptor).lookup(this);
    }

    public Invoker staticLookup(int mods, String name, String methodDescriptor) {
        return new StaticLookup(name, methodDescriptor).lookup(this);
    }

    public Invoker getMethod(String name, Class<?>[] params) {
        return new GetMethodLookup(name, params).lookup(this);
    }

    public Invoker getDeclaredMethod(String name, String paramsDescriptor) {
        return new GetDeclaredMethodLookup(name, paramsDescriptor).lookup(this);
    }

    public Invoker getDeclaredMethod(String name, Class<?>[] params) {
        return getDeclaredMethod(name, Utils.toParamDescriptor(params));
    }

    public Collection<Invoker> getMethods() {
        return new GetMethodsLookup().lookup(this);
    }

    @Override
    public String toString() {
        return "MethodProvider(" + getDottedName() + ")";
    }

}
